#pragma once

/*
 * tcp connector.
 */

#include "anet.hpp"
#include <utility>
#include "define.hpp"
#include "connection.hpp"
#include "event_loop.hpp"
#include "asio/detail/noncopyable.hpp"

namespace anet {
	namespace tcp {
		const unsigned int gClientStartId = 0x1<<0;
		class CConnector final: asio::noncopyable {
		public:
			explicit CConnector(CEventLoop &loop) : m_loop(loop), 
				m_codec(nullptr), m_session(nullptr), 
				m_conn(nullptr), m_nextId(gClientStartId) {
			}
			virtual ~CConnector() {
				connSharePtr conn = nullptr;
				bool unique = false;
				{
					std::lock_guard<std::mutex> guard(m_mutex);
					conn = m_conn;
					unique = m_conn.unique();
				}

				(void)unique;
				if (conn != nullptr) {
					conn->close();
				}
			}

		public:
			void setPacketParser(ICodec *codec) {
				assert(codec != nullptr && "packet parser is null");
				m_codec = codec;
			}
			void setSession(ISession *session) {
				assert(session != nullptr && "session is null");
				m_session = session;
			}

			ISession *getSession() {
				return m_session;
			}
			ICodec *getCodec() {
				return m_codec;
			}

		public:
			// asynchronous connect interface
			bool asyncConnect(const std::string &addr, unsigned short port, connectErrCallback errCallback) {
				if (m_session == nullptr || m_codec == nullptr) {
					if (errCallback != nullptr) {
						errCallback(addr.c_str(), port, 0);
					}
					LogCrit("net session or codec is null");
					return false;
				}

				auto &ioContext = m_loop.getNextIOContext();
				auto conn = std::make_shared<CConnection>(ioContext, m_loop);

				asio::ip::tcp::resolver resolver(ioContext);
				auto endpoints = resolver.resolve(addr, std::to_string(port));
				asio::async_connect(conn->getSocket(), endpoints, 
					[this,conn,addr,port,callback=std::move(errCallback)](auto ec,asioEndPoint point) {
					if (!ec) {
						this->saveConn(conn);
						conn->setRemoveFunc(std::bind(&CConnector::resetConn, this, std::placeholders::_1));
						conn->doStart(m_session, m_codec);
					} else {
						// error callback.
						if (callback != nullptr) {
							callback(addr.c_str(), port, ec.value());
						}
					}
				});
				return true;
			}

			// save the only conn.
			void saveConn(connSharePtr conn) {
				std::lock_guard<std::mutex> guard(m_mutex);
				auto objId = m_nextId++;
				conn->SetObjId(objId);
				this->m_conn = conn;
			}

			// reset the only conn.
			void resetConn(connSharePtr conn) {
				std::lock_guard<std::mutex> guard(m_mutex);
				if (conn.get() != this->m_conn.get()) {
					assert(false && "the two object is different");
					return;
				}
				this->m_conn.reset();
			}

			// synchronous connect interface
			bool syncConnect(const std::string &addr, unsigned short port) {
				if (m_session == nullptr || m_codec == nullptr) {
					LogCrit("net session or codec is null");
					return false;
				}

				// create a share_ptr of CConnection.
				auto &ioContext = m_loop.getNextIOContext();
				auto conn = std::make_shared<CConnection>(ioContext, m_loop);

				asio::ip::tcp::resolver resolver(ioContext);
				auto endPoint = resolver.resolve(addr, std::to_string(port));
				std::error_code ec;
				auto point = asio::connect(conn->getSocket(), endPoint, ec);
				(void)point; // avoid unused warning.
				if (!ec) {
					this->saveConn(conn);
					conn->setRemoveFunc(std::bind(&CConnector::resetConn, this, std::placeholders::_1));
					conn->doStart(m_session, m_codec);
					return true;
				} else {
					LogCrit("connect to (%s:%d) error:%d", addr.c_str(), port, ec.value());
					return false;
				}
			}

			bool Close() {
				std::lock_guard<std::mutex> guard(m_mutex);
				if (m_conn != nullptr) {
					m_conn->close();
					return true;
				} else {
					return false;
				}
			}

		private:
			CEventLoop    &m_loop;

			// the user must keep the life of session and codec.
			ICodec        *m_codec{nullptr};
			ISession      *m_session{nullptr};

			// save the CConnection.
			mutable std::mutex m_mutex;
			connSharePtr       m_conn{ nullptr };
			unsigned int       m_nextId{ gClientStartId };
		};
	}
}